详谈 CSS 选择器


本博文是学习 CSS 权威指南 读书笔记,主要讲述 CSS 的重难点及已混点。

##浅谈 CSS 历史与标签选择器,通配选择器
多年前,网页设计与网页制作方面曾经是一片混乱,由于用户对视觉效果的要求日渐增强,HTML 出现了很多规定样式的标签,例如
标签,不仅降低了工作效率,并且增加了结构的复杂性。于是在千禧年前后 CSS 面世,层叠样式表为样式/排版方面提高了不少工作效率。
那么,CSS 是怎么对 HTML 文档起作用的呢? 起先,在标签内用 style 属性,如下设置属性。

1
<p style = "width: 100px; height = 50px; ">This is Jarvis's blog</p>

通常的做法是将样式都放入一个 style 标签中。

1
2
3
4
5
6
<style>//style标签一般加在 HTML 文档的 <head> 部分中。
p {
width: 100px;
height: 50px;//句末最好加上分号,这是一个好的编程习惯,避免出现解析错误。
}
</style>

在这里出现在 style 标签中的 p 就是我们所说的标签选择器。这一规则解释为 “ HTML中所有的 p 标签 ”。
标签选择器的范围很广,像上面的规则,如果没有别的规则,则会用于文档全部的 p 标签上。
在使用电脑的时候我们在搜索一些文件的时候会用一个 “*” 的符号表示所有。而在 CSS 文件中同样有这样的存在。

1
* {color: black;}//表示所有的元素文字颜色都为黑色。

这样的选择器我们称之为通配选择器。

##css 分组与属性选择器

###分组
在写 css 样式的时候,应当分组,不应当一个一个地写,这样不便于提高效率。

1
2
3
4
5
6
7
不建议的写法:
h1 {color: red;}
h3 {color: red;}
p {color: red;}
建议的写法:
h1, h3, p {color: red;}//如果 p 元素有和 h1,h3 元素不同的样式可以加多一条规则即可,如下。
p {font-size: 14px;}

###属性选择器
说到属性选择器,我们该如何使用它呢?它适合用在批量文件中选择自己想要的文件。

1
2
3
4
img[alt="something"] {//它可以在一批的图像文件中,选择出 alt 属性为 something 的图像。
width: 300px;
height: 200px;
}

当然,接下来说的类选择器与 ID 选择器都可以用属性选择器来实现。
如果用属性选择器来选择类选择器的元素,是一个完全串匹配

##类选择器与 ID 选择器
像上面的将样式放进 style 标签中不符合模块化编程的思想,因此,现我们的做法是将 CSS 文件独立,在 HTML 文档中用 link 标签来链接一个 CSS 外部文件。

1
<link rel="stylesheet" href="css/style.css">//链接一个样式表,路径为 "css/style.css".

那么外部样式表是怎么作用到 HTML 文件中的呢? 我们可以用标签选择器来选择元素,应用样式,也可以用精准度更高的类选择器与 ID 选择器。在使用类选择器的时候,应当在标签内加上一个 class 属性。在使用 ID 选择器的时候,应当在标签内加上 id 属性。

1
2
<p class = "para">This is Jarvis's Blog</p>//在 HTML 文档中的写法
<h3 id="head"> css </h3>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
用法1
.para {//这是在 css 文件中的写法,class 在 css 文件中用 "." 来表示。
width: 100px;
height: 50px;
}
#head {// id 在 css 文件中用 "#" 来表示。
font-size: 14px;
}
用法2
p.para {
width: 100px;
height: 50px;
}
h3#head {
font-size: 14px;
}

用法 1 中规则的解释为 “所有 class 名为 para 的元素”,和 “所有 id 名为 head 的元素”,而用法 2 中规则的解释为 “所有 class 名为 para 的 p 元素”,和“所有 id 名为 head 的 h3 元素”,这里请读者注意。
并且,类选择器可以多用,中间用空格隔开,但是 id 选择器不可以多用。举个栗子:

1
<p class="para main">这里是主要的内容段落</p>//类名中间用空格隔开,一个元素可以使用多个类,但只有一个 id 。

###使用类选择器还是 ID 选择器?
很多人在刚接触 CSS 的时候,觉得 class 类选择器与 ID 选择器在应用样式的方面没有什么区别,都可以比较精准地将样式运用到具体的元素上去,导致 class 与 ID 选择器的混用与 ID 选择器的滥用。但是实际上,类选择器,顾名思义,就是类似的元素通用的样式选择,是一类的元素。而 ID 选择器是表示一个元素特有的名字,表示该元素在页面内的独有性。一般地,如果认为以后还会有类似的元素,我们都会用 class 来为元素加上样式,而在我们非常确定该元素在页面内只出现一次才会使用 ID 。这样也为编写脚本的时候能够精准地抓取到元素,而不必规定一长串的名字。

###属性选择器与 ID 选择器的区别

1
2
p[id="para"] {color: black;}
p#para {color: red;}

很多人认为这里没有区别,认为都会作用在同一个 p 标签上,但是事实上不全对,它们的确作用在同一个 p 标签上,但段落的文字颜色最终是红色 red ,为什么呢?这里会牵扯到 CSS 选择器的特殊性问题,下面会讲到,这里暂时先记住 ID 选择器要比属性选择特殊性要高,因此文字颜色是红色。

##后代选择器
浏览器在解析 HTML 和 CSS 文档中的时候,将 HTML 中的节点解析为 DOM tree 节点树,将 CSS 解析为样式结构树,将两者结合为 render tree。这样来,元素之间就有了父子关系,每一个元素要么是另一个元素的父元素,要么是另一个元素的子元素,或两者皆有。理解了这个,就可以理解后代选择器与子类选择器了。

1
2
3
4
5
6
7
8
9
10
//后代选择器
规则一:
h1 em {//当然这里的层叠关系可以更复杂,不限于两个选择器,如: ul li ol em { }
font-size: 14px;
}
//子类选择器
规则二:
h1>em {
font-size: 14px;
}

这个规则一解释为 “所有作为 h1 的子元素里面的 em 元素”。这一规则将会把 h1 子元素下的所有 em 元素找出来,无论层叠关系多么复杂。
规则二则解释为 “作为 h1 的子元素的所有 em 元素 ”,这样一来,就只会选择到 h1 元素下子元素中的第一个 em 元素,而第二、第三个及后继不受影响。

###选择相邻兄弟

1
2
3
4
5
//相邻兄弟选择器
规则三:
ul + ol {
color: gray;
}

规则三解释为 “ 选择紧接在 ul 后面出现的所有 ol 元素,且 ul 与 ol 要有相同的父元素”。既然是兄弟选择器,那么顾名思义,这个选择器只能选择兄弟元素中的第二个元素(即是上述选择器中的 ol )。但是书写顺序要按照 HTML 源顺序来书写,就是说 ol + ul 选择不到 ul + ol 想要选择的元素。如果是 li + li 这样的选择器,则第一个 li 则不受影响。

##伪类与伪元素

###伪类
伪类有很多关键字,link, visited, hover, active, first-child, lang , 在书写关键字的时候前面都必须加上 “ : “ 。所谓伪类,就是一种幻象类,平时就像不存在,但是到了某种特定的情况就会出现。举个生活中的栗子,笔者比较喜欢篮球,我们不知道篮球比赛在什么时候会打进一些好球,但是打进好球的时候我们都会欢呼尖叫。
而伪类我们看得最多体现的时候,是在 a 标签上面的使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//静态伪类
a:link {color: bule;}//超链接

a:visited {color: gray;}//已访问的链接
//动态伪类
a:hover {color: red;}//当鼠标悬停在链接上

input:focus {color: Violet;}//当焦点在 input 标签上

a:active {color: yellow;}//正在访问的链接
//另外一些伪类
p:first-child {font-size: 14px;}//选择第一个子元素
//一些运用
a:lang(fr):hover {color: green;}//选择语言为法文的链接采取样式

书写的建议是 : link -> visited -> hover -> active 。
这里要注意的是 first-child 这一个伪类。看下面的栗子:

1
2
3
4
5
6
7
8
<div>
<p>This is a Blog</p>
<ul>
<li> CSS </li>
<li> HTML </li>
<ul>
<p>The end</p>
</div>

如果有一个伪类写成

1
2
p:first-child {font-weight: bold;} 
li:first-child {text-transform: uppercase; }

这样的话,常见的错误就会认为这些规则会对 p 标签和 li 标签下的第一个子元素起作用。实际上这些规则解读是: 作为某元素(在这里是 div )的第一个子元素的所有 p 元素/作为某元素(在 HTML 中当然是 ul 或 ol)的第一个子元素的所有 li 元素,这样,只会对上述栗子中的第一个 p 标签起作用(文字变粗),列表的第一项 li 起作用(文字大写)。

###伪元素
与伪类相似,伪元素在书写关键字的时候,前面也要加上 “ : “。关键字有 first-letter,first-line,before,after。伪元素可以插入假想的元素。

1
2
3
4
5
6
7
p:first-letter {font-size: 15px;}//对段落的首字母大写。

p:first-line {text-transform: uppercase;}//对段落的第一行文字产生倾斜效果。

p:before {content: "}}";}//在段落前面插入内容 }}

p:after {content: " the end.";}//在段落后面插入内容 the end.

不过 first-letter,first-line 只能对块级元素或标记起作用,对超链接等行内元素没有作用。并且伪元素主体是选择器最后一个元素。

##选择器优先级

###特殊性
在 CSS 里面,分为几个特殊性等级,分别用不同的数字来标记:

1
2
3
4
5
6
7
8
9
10
11
1. ID 选择器为 0100
2. 类选择器,属性选择,伪类为 0010
3. 元素(即标签),伪元素为 0001
而结合符,通配选择器的优先级为 0000,对值没有贡献

栗子:
p em {color: red;} // 特殊性为 0,0,0,2
#para {color: black; }//特殊性为 0,1,0,0
p#para {color: black;}//特殊性为 0,1,0,1
.Jarvis {color: yellow;}//特殊性为 0,0,1,0
div.Jarvis {color: gray;}//特殊性为 0,0,1,1

上面的数字标记,按照从左到右的排序,值的大小递减,也就是说,0,1,0,0 要比 0,0,1,0 要大。
而像上面所说的,通配选择器的特殊性为 0,0,0,0 ,很多人认为这里没有值得注意的,恰恰相反,这里要注意的是通配选择器的特殊性要比无特殊性的值要高,比如继承的值,就是无特殊性的。
细心的读者会发现,这里的数字标记最高位都为 0 ,当它为 1 的时候呢?没错,当 HTML 文档中有内联样式的时候,特殊性为 1,0,0,0 。。但是大家不要把这里的数字标记与十进制的 100 和 10 混淆,那是不同的,下面会有例子说明。

1
2
3
4
5
6
7
8
9
每一对选择器都是选择同一个元素
h1 {color: black;} /* 0,0,0,1 */
body h1 {color: bule;}/* 0,0,0,2 (winner)*/

#para {color: black; }/* 0,1,0,0 */
p#para {color: black;}/* 0,1,0,1 (winner) */

html > body table tr[id="totals"] td ul > li {color: maroon;}/* 0,0,1,7 */
li#answer {color: navy;}/* 0,1,0,1 (winner)*/

大家明白这里的数字与十进制是不同了吗?如果还是不理解,请看下面的栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//都是选择第十个 div 里面的内容
<style>
.ten {
color: red;
}
body div div div div div div div div div div {
color: yellow;
}
</style>
<body>
<div>
1
<div>
2
<div>
3
<div>
4
<div>
5
<div>
6
<div>
7
<div>
8
<div>
9
<div class="ten">
10
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</body>

这里的第二个规则的特殊性是 0,0,0,11,而第一个规则的特殊性是 0,0,1,0,如果看作是十进制的话,字体颜色将是黄色,但是实际上,字体颜色是红色,那就代表高一位的数字大于低位的数字,无论低位数字多大。

###重要声明
重要声明采取 !important 来声明,放在一个声明分号之前。

1
p {color: red !important ;}

前面的栗子中都是非重要声明,重要声明没有特殊性,但在重要声明与非重要声明冲突的时候,重要声明总是胜出。非重要声明是以特殊性解决冲突的。

###继承
前面我们说父子关系的时候,曾经理解过 HTML 文档的树状结构,这里同样,继承也是建立在这个理解之上的。
所谓继承,也就是样式继承,就是说样式在父元素中设置,如果子元素没有设置相应的样式的话,将会从父元素上继承相对应的值,直到子元素下再无子元素为止。
一般来说,继承是向下继承,不会向上继承,不过有一个例外 body 的背景样式可以传到 HTML 元素上。
继承的值没有特殊性,通配选择器的值要优先于继承值,因为通配选择器的优先级为 0,0,0,0 。
继承不会继承框模型的值(外边距,内边距,border,背景),因为假设父元素设置了左边距 30px ,如果子元素都会有一个左边距 30px,就会增添创作人员不必要的麻烦,必须一个个消除边距。

###层叠

终于,我们要说到层叠了,层叠样式表怎么能不讲层叠? 在规则起冲突的时候,到底是哪一个规则比较优先呢?
有几个规则:
1.按照权重和来源排序
所谓权重,也就是前面说讲的重要性声明的问题。来源包括创作人员与读者,创作人员优于读者。但读者重要声明拥有最高等级。

2.按照特殊性排序
这里前面已经讲了,高特殊性优于低特殊性。

3.按照顺序排序
举个例子:

1
2
h1 {color: red;}
h1 {color: green;}

标题 1 的文字颜色不可能又红又绿,以上这两条规则,将会是第二条规则优先,因为从上至下,第二条规则的样式将会覆盖第一条规则的样式。这也是为什么建议链接伪类按照 link -> visited -> hover -> active 的顺序来写。如果你写成这样

1
2
3
4
5
6
//伪类加到同一个属性上(如下),不同属性不起冲突
:active {color: red;}
:hover {color: bule;}
:link {color: black;}
:visited {color: gray;}
//这样 active 和 hover 的样式将不会呈现。因为所有链接都处于两种状态:链接和已被链接,覆盖了 hover 和 active 样式


作者 张翔
2015 年 10月 08日